/*
 * Galaxium Messenger
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;

using GLib;
using Gtk;

using Anculus.Core;

namespace Galaxium.Gui.GtkGui
{
	public partial class GalaxiumTreeView : TreeView
	{
		struct TreeRow
		{
			public object Data;
		}
		
#region Private Fields
		TreeStore _store;
		TreeModelSort _sortedModel;
		TreeModelFilter _filterModel;
		
		bool _sort = true;
		SortOrder _sortOrder;
		
		string _filterString = null;
		bool _filterCaseSensitive = false;
#endregion
		
#region Properties
		public bool Sort
		{
			get { return _sort; }
			set
			{
				if (_sort == value)
					return;
				
				_sort = value;
				
				if (_sort)
					Resort ();
			}
		}
		
		public SortOrder SortOrder
		{
			get { return _sortOrder; }
			set
			{
				if (_sortOrder == value)
					return;
				
				_sortOrder = value;
				
				if (_sort)
					_sortedModel.SetSortColumnId (0, _sortOrder == SortOrder.Ascending ? SortType.Ascending : SortType.Descending);
			}
		}
		
		public string Filter
		{
			get { return _filterString; }
			set
			{
				if (_filterString == value)
					return;
				
				_filterString = value;
				Refilter ();
			}
		}
		
		public bool FilterCaseSensitive
		{
			get { return _filterCaseSensitive; }
			set
			{
				if (_filterCaseSensitive == value)
					return;
				
				_filterCaseSensitive = value;
				Refilter ();
			}
		}
		
		public TreeStore Store
		{
			get { return _store; }
		}
#endregion
		
		public GalaxiumTreeView ()
		{
			_store = new TreeStore (typeof (TreeRow));
			
			_sortedModel = new TreeModelSort (_store);
			_sortedModel.SetSortFunc (0, SortedModelSortFunc);
			
			_filterModel = new TreeModelFilter (_sortedModel, null);
			_filterModel.VisibleFunc = new TreeModelFilterVisibleFunc (FilterModelVisibleFunc);
			
			Model = _filterModel;
		}
		
#region Sorting
		public virtual void Resort ()
		{
			if (_sort)
			{
				//Anculus.Core.Log.Debug ("Resort");
				
				//TODO: the following is a really ugly hack... is there no better way to force a resort?
				_sortedModel.SetSortColumnId (0, _sortOrder == SortOrder.Ascending ? SortType.Descending : SortType.Ascending);
				_sortedModel.SetSortColumnId (0, _sortOrder == SortOrder.Ascending ? SortType.Ascending : SortType.Descending);
			}
		}
		
		int SortedModelSortFunc (TreeModel model, TreeIter iter1, TreeIter iter2)
		{
			if (model == null)
				return 0;
			
			object data1 = GetModelData (model, iter1);
			object data2 = GetModelData (model, iter2);
			
			//Anculus.Core.Log.Debug ("Compare {0} to {1}", data1, data2);
			
			if (data1 == null)
			{
				if (data2 == null)
					return 0;
				else
					return 1;
			}
			else if (data2 == null)
				return -1;
			
			return Compare (data1, data2);
		}
		
		protected virtual int Compare (object obj1, object obj2)
		{
			return obj1.ToString ().CompareTo (obj2.ToString ());
		}
#endregion
		
#region Filtering
		public virtual void Refilter ()
		{
			_filterModel.Refilter ();
		}
		
		bool FilterModelVisibleFunc (TreeModel model, TreeIter iter)
		{
			if (string.IsNullOrEmpty (_filterString))
				return true;
			
			object data = GetModelData (model, iter);
			
			return FilterShouldDisplay (data);
		}
		
		protected virtual bool FilterShouldDisplay (object data)
		{
			return true;
		}
#endregion
		
#region Row Handling
		public TreeRowReference AddRow (object data)
		{
			TreeRow treeRow = new TreeRow ();
			treeRow.Data = data;
			
			TreeIter iter = _store.AppendValues (treeRow);
			return new TreeRowReference (_store, _store.GetPath (iter));
		}
		
		public TreeRowReference AddRow (TreeRowReference parent, object data)
		{
			TreeIter parentIter;
			_store.GetIter (out parentIter, parent.Path);
			
			TreeIter iter = _store.AppendNode (parentIter);
			SetModelData (_store, iter, data);
			return new TreeRowReference (_store, _store.GetPath (iter));
		}
		
		public void RemoveRow (TreeRowReference rowRef)
		{
			TreeIter iter;
			_store.GetIter (out iter, rowRef.Path);
			_store.Remove (ref iter);
		}
		
		public void RowChanged (TreeRowReference rowRef)
		{
			TreeIter iter;
			TreePath path = rowRef.Path;
			_store.GetIter (out iter, path);
			_store.EmitRowChanged (path, iter);
		}
#endregion
		
#region Row Data
		public object GetData (TreeRowReference rowRef)
		{
			return GetData (rowRef.Path);
		}
		
		public object GetData (TreePath path)
		{
			TreeIter iter;
			_store.GetIter (out iter, path);
			
			return GetModelData (_store, iter);
		}
		
		public object GetModelData (TreePath path)
		{
			TreeIter iter;
			if (!Model.GetIter (out iter, path))
				return null;
			
			return GetModelData (iter);
		}
		
		public object GetModelData (TreeIter iter)
		{
			return GetModelData (Model, iter);
		}
		
		public object GetModelData (TreeModel model, TreeIter iter)
		{
			object val = model.GetValue (iter, 0);
			
			if (val is TreeRow)
				return ((TreeRow)val).Data;
			
			return null;
		}
		
		public void SetData (TreeRowReference rowRef, object data)
		{
			SetData (rowRef.Path, data);
		}
		
		public void SetData (TreePath path, object data)
		{
			TreeIter iter;
			_store.GetIter (out iter, path);
			
			SetModelData (_store, iter, data);
		}
		
		public void SetModelData (TreePath path, object data)
		{
			TreeIter iter;
			if (!Model.GetIter (out iter, path))
				return;
			
			SetModelData (iter, data);
		}
		
		public void SetModelData (TreeIter iter, object data)
		{
			SetModelData (Model, iter, data);
		}
		
		public void SetModelData (TreeModel model, TreeIter iter, object data)
		{
			TreeRow row = new TreeRow ();
			row.Data = data;
			
			model.SetValue (iter, 0, row);
		}
#endregion
		
		public TreePath GetModelPath (TreePath dataPath)
		{
			return _sortedModel.ConvertChildPathToPath (_filterModel.ConvertChildPathToPath (dataPath));
		}
		
		protected void AllRowsChanged ()
		{
			Store.Foreach (delegate (TreeModel model, TreePath path, TreeIter iter)
			{
				model.EmitRowChanged (path, iter);
				return false;
			});
		}
		
		protected virtual Menu CreateContextMenu (TreePath treePath, object data)
		{
			return null;
		}
		
		protected override bool OnButtonPressEvent (Gdk.EventButton e)
		{
			bool retVal = base.OnButtonPressEvent (e);
			
			if (e.Button != 3)
				return retVal;
			
			TreePath path;
			if (!GetPathAtPos ((int)e.X, (int)e.Y, out path))
				return retVal;
			
			object val = GetModelData (path);
			Menu menu = CreateContextMenu (path, val);
			
			if (menu != null)
			{
				menu.Popup (null, null, null, e.Button, e.Time);
				menu.ShowAll ();
			}
			
			return retVal;
		}
	}
}
